import katex from 'katex';
import Quill from 'quill';
import { getChromeVersion, htmlEscape } from '../utils/index';
import { getBaseStyleText, getFontStyleText } from './FormulaStyle';

let extended = false;
const QuillFormula = Quill.import('formats/formula');

// 数学公式支持插件
// 该插件在富文本模式下可用
class Formula {
    //  构造函数
    constructor(opt) {
        this.opt = opt;
        this.mindMap = opt.mindMap;
        window.katex = katex;
        this.init();
        this.config = this.getKatexConfig();
        this.cssEl = null;
        this.addStyle();
        this.extendQuill();
        this.onDestroy = this.onDestroy.bind(this);
        this.mindMap.on('beforeDestroy', this.onDestroy);
    }

    onDestroy() {
        const instanceCount = Object.getPrototypeOf(this.mindMap).constructor
            .instanceCount;
        // 如果思维导图实例数量变成0了，那么就恢复成默认的
        if (instanceCount <= 1) {
            extended = false;
            Quill.register('formats/formula', QuillFormula, true);
        }
    }

    init() {
        if (this.mindMap.opt.enableEditFormulaInRichTextEdit) {
            this.mindMap.opt.transformRichTextOnEnterEdit =
                this.latexRichToText.bind(this);
            this.mindMap.opt.beforeHideRichTextEdit =
                this.formatLatex.bind(this);
        }
    }

    // 获取katex配置
    getKatexConfig() {
        const config = {
            throwOnError: false,
            errorColor: '#f00',
            output: 'mathml', // 默认只输出公式
        };
        let { getKatexOutputType } = this.mindMap.opt;
        getKatexOutputType =
            getKatexOutputType ||
            function () {
                // Chrome内核100以下，mathml配置公式无法正确渲染
                const chromeVersion = getChromeVersion();
                if (chromeVersion && chromeVersion <= 100) {
                    return 'html';
                }
            };
        const output = getKatexOutputType() || 'mathml';
        config.output = ['mathml', 'html'].includes(output) ? output : 'mathml';
        return config;
    }

    // 修改formula格式工具
    extendQuill() {
        if (extended) return;
        extended = true;

        const self = this;

        class CustomFormulaBlot extends QuillFormula {
            static create(value) {
                let node = super.create(value);
                if (typeof value === 'string') {
                    katex.render(value, node, self.config);
                    node.setAttribute('data-value', htmlEscape(value));
                }
                return node;
            }
        }

        Quill.register('formats/formula', CustomFormulaBlot, true);
    }

    getStyleText() {
        const { katexFontPath } = this.mindMap.opt;
        let text = '';
        if (this.config.output === 'html') {
            text = getFontStyleText(katexFontPath);
        }
        text += getBaseStyleText();
        return text;
    }

    addStyle() {
        this.cssEl = document.createElement('style');
        this.cssEl.type = 'text/css';
        this.cssEl.innerHTML = this.getStyleText();
        document.head.appendChild(this.cssEl);
    }

    removeStyle() {
        document.head.removeChild(this.cssEl);
    }

    // 给指定的节点插入指定公式
    insertFormulaToNode(node, formula) {
        let richTextPlugin = this.mindMap.richText;
        richTextPlugin.showEditText({ node });
        richTextPlugin.quill.insertEmbed(
            richTextPlugin.quill.getLength() - 1,
            'formula',
            formula,
        );
        richTextPlugin.setTextStyleIfNotRichText(richTextPlugin.node);
        richTextPlugin.hideEditText([node]);
    }

    // 将公式富文本转换为公式源码
    latexRichToText(nodeText) {
        if (nodeText.indexOf('class="ql-formula"') !== -1) {
            const parser = new DOMParser();
            const doc = parser.parseFromString(nodeText, 'text/html');
            const els = doc.getElementsByClassName('ql-formula');
            for (const el of els)
                nodeText = nodeText.replace(
                    el.outerHTML,
                    `\$${el.getAttribute('data-value')}\$`,
                );
        }
        return nodeText;
    }

    // 使用格式化的 latex 字符串内容更新 quill 内容：输入 $*****$
    formatLatex(richText) {
        const contents = richText.quill.getContents();
        const ops = contents.ops;
        let mod = false;
        for (let i = ops.length - 1; i >= 0; i--) {
            const op = ops[i];
            const insert = op.insert;
            if (insert && typeof insert !== 'object' && insert !== '\n') {
                if (/\$.+?\$/g.test(insert)) {
                    const m = [...insert.matchAll(/\$.+?\$/g)];
                    const arr = insert.split(/\$.+?\$/g);
                    for (let j = m.length - 1; j >= 0; j--) {
                        const exp =
                            m[j] && m[j][0]
                                ? m[j][0].slice(1, -1) || null
                                : null; // $...$ 之间的表达式
                        if (exp !== null && exp.trim().length > 0) {
                            const isLegal = this.checkFormulaIsLegal(exp);
                            if (isLegal) {
                                arr.splice(j + 1, 0, {
                                    insert: { formula: exp },
                                }); // 添加到对应位置之后
                                mod = true;
                            } else {
                                arr.splice(j + 1, 0, '');
                            }
                        } else arr.splice(j + 1, 0, ''); // 表达式为空时，占位
                    }
                    while (arr.length > 0) {
                        let v = arr.pop();
                        if (typeof v === 'string') {
                            if (v.length < 1) continue;
                            v = { insert: v };
                        }
                        v['attributes'] = ops[i]['attributes'];
                        ops.splice(i + 1, 0, v);
                    }
                    ops.splice(i, 1); // 删除原来的字符串
                }
            }
        }
        if (mod) richText.quill.setContents(contents);
    }

    checkFormulaIsLegal(str) {
        try {
            katex.renderToString(str);
            return true;
        } catch (e) {
            return false;
        }
    }

    // 插件被移除前做的事情
    beforePluginRemove() {
        this.removeStyle();
        this.mindMap.off('beforeDestroy', this.onDestroy);
    }

    // 插件被卸载前做的事情
    beforePluginDestroy() {
        this.removeStyle();
        this.mindMap.off('beforeDestroy', this.onDestroy);
    }
}

Formula.instanceName = 'formula';

export default Formula;
